#include "stdafx.h"
#include "hwinio.h"
#include "hwinexception.h"
#include "hwincomobject.h"

namespace harlinn
{
    namespace windows
    {
        namespace io
        {
            namespace
            {
                wchar_t TrimEndChars[] = { 0x9, 0xA, 0xB, 0xC, 0xD, 0x20, 0x85, 0xA0};

                wchar_t RealInvalidPathChars[] = { '\"', '<', '>', '|', '\0', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }; 

                wchar_t InvalidFileNameChars[] = { '\"', '<', '>', '|', '\0', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, ':', '*', '?', '\\', '/' }; 


                inline void CheckInvalidPathChars(const String& path)
                {
                    if(path.IndexOf([] (wchar_t c) -> bool
                        {  
                            return (c == '\"' || c == '<' || c == '>' || c == '|' || c < 32);
                        }) != String::npos)
                    {
                        throw ArgumentException("Invalid path character");
                    }
                }



            }


            // ----------------------------------------------------------------------
            // StreamBase
            // ----------------------------------------------------------------------

            HWIN_EXPORT ComObject* StreamBase::CreateComObject() const
            {
                ComStream* result = new ComStream(std::const_pointer_cast<StreamBase,const StreamBase>( As<StreamBase>() ));
                return result;
            }

            HWIN_EXPORT StreamBase& StreamBase::Initialize()
            {
                Base::Initialize();
                return *this;
            }


            HWIN_EXPORT long long StreamBase::Position() const
            {
                return const_cast<StreamBase*>(this)->Seek(0,SeekOrigin::CurrentPosition);
            }

            HWIN_EXPORT StreamBase& StreamBase::SetPosition(long long thePosition)
            {
                this->Seek(thePosition,SeekOrigin::StartOfFile);
                return *this;
            }

            HWIN_EXPORT long long StreamBase::Size() const
            {
                long long currentPosition = this->Position();
                long long result = const_cast<StreamBase*>(this)->Seek(0,SeekOrigin::EndOfFile);
                const_cast<StreamBase*>(this)->Seek(currentPosition,SeekOrigin::StartOfFile);
                return result;
            }

            HWIN_EXPORT StreamBase& StreamBase::SetSize(long long theSize)
            {
                // this is intentionally a nop
                return *this;
            }

            HWIN_EXPORT StreamBase& StreamBase::Flush()
            {
                // this is intentionally a nop
                return *this;
            }


            // -----------------------------------------------------------------
            // FileSystemEntry
            // -----------------------------------------------------------------
            HWIN_EXPORT FileSystemEntry::FileSystemEntry(const String& theParentDirectory, const String& theName, FileAttributes theAttributes, 
                    const DateTime& theCreationTime, const DateTime& theLastAccessTime, const DateTime& theLastWriteTime)
                    : parentDirectory(theParentDirectory), name(theName), attributes(theAttributes), 
                      creationTime(theCreationTime),lastAccessTime(theLastAccessTime), lastWriteTime(theLastWriteTime)
            {}

            // -----------------------------------------------------------------
            // FileInfo
            // -----------------------------------------------------------------
            HWIN_EXPORT FileInfo::FileInfo(const String& theParentDirectory, const String& theName, FileAttributes theAttributes, 
                    const DateTime& theCreationTime, const DateTime& theLastAccessTime, const DateTime& lastWriteTime,
                    long long theFileSize)
                    : Base(theParentDirectory, theName, theAttributes, theCreationTime, theLastAccessTime, lastWriteTime),
                      fileSize(theFileSize)
            {}


            // -----------------------------------------------------------------
            // DirectoryInfo
            // -----------------------------------------------------------------
            HWIN_EXPORT DirectoryInfo::DirectoryInfo(const String& theParentDirectory, const String& theName, FileAttributes theAttributes, 
                    const DateTime& theCreationTime, const DateTime& theLastAccessTime, const DateTime& lastWriteTime)
                    : Base(theParentDirectory, theName, theAttributes, theCreationTime, theLastAccessTime, lastWriteTime)
            {}

            // -----------------------------------------------------------------
            // FileSystemEntriesBase
            // -----------------------------------------------------------------
            HWIN_EXPORT FileSystemEntriesBase::FileSystemEntriesBase(const String& thePath, const String& theSearchPattern )
                : path(thePath),searchPattern(theSearchPattern),searchHandle(INVALID_HANDLE_VALUE)
            {
                path.TrimRight(L"\\/",2);
                path = Path::GetFullPathName(path);
            }

            HWIN_EXPORT FileSystemEntriesBase::FileSystemEntriesBase(const String& theSearchPattern )
                : searchPattern(theSearchPattern),
                  searchHandle(INVALID_HANDLE_VALUE)
            {
                path = Directory::GetCurrentDirectory();    
            }

            HWIN_EXPORT FileSystemEntriesBase::~FileSystemEntriesBase()
            {
                if(searchHandle != INVALID_HANDLE_VALUE)
                {
                    FindClose(searchHandle);
                }
            }

            HWIN_EXPORT bool FileSystemEntriesBase::Read()
            {
                current.reset();
                memset(&data,0,sizeof(WIN32_FIND_DATA));
                if(searchHandle == INVALID_HANDLE_VALUE)
                {
                    String s = path;
                    s += L'\\';
                    s += searchPattern;

                    searchHandle = FindFirstFileExW(s.c_str(),FindExInfoBasic,&data,FindExSearchNameMatch,nullptr,0);
                    if(searchHandle == INVALID_HANDLE_VALUE)
                    {
                        if(GetLastError() != ERROR_FILE_NOT_FOUND)
                        {
                            ThrowLastOSError();
                        }
                        else
                        {
                            return false;
                        }
                    }
                    return true;
                }
                else
                {
                    if(FindNextFileW(searchHandle,&data) == 0)
                    {
                        if(GetLastError() != ERROR_FILE_NOT_FOUND)
                        {
                            ThrowLastOSError();
                        }
                        else
                        {
                            return false;
                        }
                    }
                    return true;
                }
            }

            HWIN_EXPORT String FileSystemEntriesBase::Name() const
            {
                return data.cFileName;
            }
            HWIN_EXPORT String FileSystemEntriesBase::FullPath() const
            {
                String result = path;
                result += '\\';
                result += data.cFileName;
                return result;
            }


            // -----------------------------------------------------------------
            // Path
            // -----------------------------------------------------------------
            HWIN_EXPORT String Path::ChangeExtension(const String& path, const String& newExtension)
            {
                if (path) 
                { 
                    CheckInvalidPathChars(path);
                    wchar_t stopCharacters[] = {'.', DirectorySeparatorChar , AltDirectorySeparatorChar, VolumeSeparatorChar,'\x00'};


                    String::size_type index = path.LastIndexOfAnyOf(stopCharacters);
                    String result = path; 
                    if(index != String::npos)
                    {
                        if(path[index] == '.')
                        {
                            result = path.SubString(0, index);
                        }
                    }

                    if(newExtension)
                    {
                        if(newExtension[0] != '.')
                        {
                            result += '.';
                        }
                        result += newExtension;
                    }
                    return result;
                } 
                return String();
            }

            HWIN_EXPORT String Path::GetLongPathName(const String& path)
            {
                if(path)
                {
                    wchar_t buffer[MAX_PATH+1] = {0,};
                    auto length = ::GetLongPathNameW(path.c_str(),buffer,sizeof(buffer)/sizeof(wchar_t));
                    if(length == 0)
                    {
                        ThrowLastOSError();
                    }
                    if(length > (sizeof(buffer)/sizeof(wchar_t)))
                    {
                        String result;
                        result.SetLength(length-1);
                        length = ::GetLongPathNameW(path.c_str(),result.c_str(),length);
                        if(length == 0)
                        {
                            ThrowLastOSError();
                        }
                        return result;
                    }
                    else
                    {
                        String result(buffer,length);
                        return result;
                    }
                }
                return String();
            }

            HWIN_EXPORT String Path::GetFullPathName(const String& path)
            {
                if(path)
                {
                    LPWSTR filePart;
                    wchar_t buffer[MAX_PATH+1] = {0,};
                    auto length = ::GetFullPathNameW(path.c_str(),sizeof(buffer)/sizeof(wchar_t),buffer,&filePart);
                    if(length == 0)
                    {
                        ThrowLastOSError();
                    }
                    if(length >= (sizeof(buffer)/sizeof(wchar_t)))
                    {
                        String result;
                        result.SetLength(length-1);
                        length = ::GetFullPathNameW(path.c_str(),length,result.c_str(),&filePart);
                        if(length == 0)
                        {
                            ThrowLastOSError();
                        }
                        return result;
                    }
                    else
                    {
                        String result(buffer,length);
                        return result;
                    }
                }
                return String();
            }
            HWIN_EXPORT String Path::GetFullPathName(const String& path,String::size_type& indexOfFileName)
            {
                if(path)
                {
                    LPWSTR filePart;
                    wchar_t buffer[MAX_PATH+1] = {0,};
                    auto length = ::GetFullPathNameW(path.c_str(),sizeof(buffer)/sizeof(wchar_t),buffer,&filePart);
                    if(length == 0)
                    {
                        ThrowLastOSError();
                    }
                    if(length >= (sizeof(buffer)/sizeof(wchar_t)))
                    {
                        String result;
                        result.SetLength(length-1);
                        length = ::GetFullPathNameW(path.c_str(),length,result.c_str(),&filePart);
                        if(length == 0)
                        {
                            ThrowLastOSError();
                        }
                        indexOfFileName = filePart - result.c_str();
                        return result;
                    }
                    else
                    {
                        indexOfFileName = filePart - buffer;
                        String result(buffer,length);
                        return result;
                    }
                }
                indexOfFileName = String::npos;
                return String();
            }

            // -----------------------------------------------------------------
            // File
            // -----------------------------------------------------------------
            namespace
            {
                bool ExistsLockedOrShared(const wchar_t* path)
                {
                    WIN32_FIND_DATAW FindData;
                    HANDLE hFind;
                    bool result = false;
                    // Either the file is locked/share_exclusive or we got an access denied 
                    hFind = FindFirstFileW(path,&FindData );
                    if( hFind != INVALID_HANDLE_VALUE)
                    {
                        FindClose(hFind);
                        result = (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
                    }
                    return result;
                }
            }
            HWIN_EXPORT bool File::Exist(const String& path)
            {
                if(path)
                {
                    auto attrs = GetFileAttributesW(path.c_str());
                    if(attrs != INVALID_FILE_ATTRIBUTES)
                    {
                        if((attrs & FILE_ATTRIBUTE_DIRECTORY) == 0)
                        {
                            return true;
                        }
                    }
                    else
                    {
                        auto lastError = GetLastError();
                        if((lastError != ERROR_FILE_NOT_FOUND)&&
                            (lastError != ERROR_PATH_NOT_FOUND)&&
                            (lastError != ERROR_INVALID_NAME))
                        {
                            return ExistsLockedOrShared(path.c_str());
                        }

                    }
                }
                return false;
            }

            HWIN_EXPORT bool File::Exist(const wchar_t* path)
            {
                if(path && path[0])
                {
                    auto attrs = GetFileAttributesW(path);
                    if(attrs != INVALID_FILE_ATTRIBUTES)
                    {
                        if((attrs & FILE_ATTRIBUTE_DIRECTORY) == 0)
                        {
                            return true;
                        }
                    }
                    else
                    {
                        auto lastError = GetLastError();
                        if((lastError != ERROR_FILE_NOT_FOUND)&&
                            (lastError != ERROR_PATH_NOT_FOUND)&&
                            (lastError != ERROR_INVALID_NAME))
                        {
                            return ExistsLockedOrShared(path);
                        }

                    }
                }
                return false;
            }


            // -----------------------------------------------------------------
            // Directory
            // -----------------------------------------------------------------
            HWIN_EXPORT String Directory::GetCurrentDirectory()
            {
                wchar_t buffer[MAX_PATH+1] = {0,};
                auto length = ::GetCurrentDirectory(sizeof(buffer)/sizeof(wchar_t),buffer);
                if(length == 0)
                {
                    ThrowLastOSError();
                }
                if(length > (sizeof(buffer)/sizeof(wchar_t)))
                {
                    String result;
                    result.SetLength(length-1);
                    length = ::GetCurrentDirectory(length,result.c_str());
                    if(length == 0)
                    {
                        ThrowLastOSError();
                    }
                    return result;
                }
                else
                {
                    String result(buffer,length);
                    return result;
                }
            }


            HWIN_EXPORT bool Directory::Exist(const String& path)
            {
                if(path)
                {
                    auto attrs = GetFileAttributesW(path.c_str());
                    if(attrs != INVALID_FILE_ATTRIBUTES)
                    {
                        if((attrs & FILE_ATTRIBUTE_DIRECTORY) != 0)
                        {
                            return true;
                        }
                    }
                }
                return false;
            }

            HWIN_EXPORT bool Directory::Exist(const wchar_t* path)
            {
                if(path)
                {
                    auto attrs = GetFileAttributesW(path);
                    if(attrs != INVALID_FILE_ATTRIBUTES)
                    {
                        if((attrs & FILE_ATTRIBUTE_DIRECTORY) != 0)
                        {
                            return true;
                        }
                    }
                }
                return false;
            }

            // -----------------------------------------------------------------
            // StreamCore
            // -----------------------------------------------------------------

            HWIN_EXPORT StreamCore::StreamCore(HANDLE theFileHandle)
                : hFile(theFileHandle)
            {}

            HWIN_EXPORT StreamCore::~StreamCore()
            {
                if(hFile != INVALID_HANDLE_VALUE)
                {
                    CloseHandle(hFile);
                    hFile = INVALID_HANDLE_VALUE;
                }
            }

            HWIN_EXPORT StreamBase& StreamCore::SetPosition(long long thePosition)
            {
                Seek(thePosition,SeekOrigin::StartOfFile);
                return *this;
            }

            HWIN_EXPORT long long StreamCore::Size() const
            {
                LARGE_INTEGER llFileSize;
                llFileSize.QuadPart = 0;
                if(GetFileSizeEx(hFile,&llFileSize) == 0)
                {
                    ThrowLastOSError();
                }
                return llFileSize.QuadPart;
            }
            HWIN_EXPORT StreamBase& StreamCore::SetSize(long long theSize)
            {
                long long currentPosition = this->Position();
                long long newPosition = Seek(theSize,SeekOrigin::StartOfFile);
                SetEndOfFile(hFile);
                if(currentPosition < newPosition)
                {
                    Seek(currentPosition,SeekOrigin::StartOfFile);
                }
                return *this;
            }

            HWIN_EXPORT StreamBase& StreamCore::Flush()
            {
                if(FlushFileBuffers(hFile) == 0)
                {
                    ThrowLastOSError();
                }
                return *this;
            }

            HWIN_EXPORT long long StreamCore::Read(void* buffer, size_t numberOfBytesToRead )
            {
                DWORD numberOfBytesRead = 0;
                if(ReadFile(hFile,buffer,DWORD(numberOfBytesToRead),&numberOfBytesRead,nullptr) == 0)
                {
                    ThrowLastOSError();
                }
                return numberOfBytesRead;
            }

            HWIN_EXPORT long long StreamCore::Write(const void* buffer, size_t numberOfBytesToWrite )
            {
                DWORD numberOfBytesWritten = 0;
                if(WriteFile(hFile,buffer,DWORD(numberOfBytesToWrite),&numberOfBytesWritten,nullptr) == 0)
                {
                    ThrowLastOSError();
                }
                return numberOfBytesWritten;
            }

            HWIN_EXPORT long long StreamCore::Seek(long long offset, SeekOrigin seekOrigin)
            {
                LARGE_INTEGER llOffset;
                llOffset.QuadPart = offset;
                LARGE_INTEGER llNewPosition;
                llNewPosition.QuadPart = 0;
                if(SetFilePointerEx(hFile,llOffset,&llNewPosition,DWORD(seekOrigin)) == 0)
                {
                    ThrowLastOSError();
                }
                return llNewPosition.QuadPart;
            }

            HWIN_EXPORT std::shared_ptr<StreamBase> StreamCore::Clone() const
            {
                HANDLE newHandle = 0;
                if(::DuplicateHandle(GetCurrentProcess(),hFile,GetCurrentProcess(),&newHandle,0,FALSE,DUPLICATE_SAME_ACCESS) == 0)
                {
                    ThrowLastOSError();
                }
                auto result = std::make_shared<StreamCore>(newHandle);
                return result;
            }

            // -----------------------------------------------------------------
            // FileStream
            // -----------------------------------------------------------------

            HWIN_EXPORT HANDLE FileStream::Create(LPCWSTR lpFileName, FileAccess fileAccess, FileShare fileShare, LPSECURITY_ATTRIBUTES lpSecurityAttributes,FileMode fileMode,FileAttributes attributes,FileOptions fileOptions)
            {
                DWORD creationDisposition = 0;
                if(fileMode != FileMode::Append)
                {
                    creationDisposition = DWORD(fileMode);
                }
                else
                {
                    creationDisposition = DWORD(FileMode::OpenOrCreate);
                }
                DWORD flagsAndAttributes = DWORD(attributes) | DWORD(fileOptions);
                HANDLE result = ::CreateFile(lpFileName,DWORD(fileAccess),DWORD(fileShare),lpSecurityAttributes,creationDisposition,flagsAndAttributes,nullptr);

                if(result == INVALID_HANDLE_VALUE)
                {
                    ThrowLastOSError();
                }

                if(fileMode == FileMode::Append)
                {
                    LARGE_INTEGER liDistanceToMove;
                    liDistanceToMove.QuadPart = 0;
                    if(SetFilePointerEx(result,liDistanceToMove,nullptr,FILE_END) == 0)
                    {
                        CloseHandle(result);
                        ThrowLastOSError();
                    }
                }

                return result;
            }

            HWIN_EXPORT FileStream::FileStream(const String& fileName, FileAccess fileAccess, FileShare fileShare, LPSECURITY_ATTRIBUTES lpSecurityAttributes,FileMode fileMode,FileAttributes attributes,FileOptions fileOptions)
                : Base(Create(fileName.c_str(), fileAccess, fileShare, lpSecurityAttributes,fileMode,attributes,fileOptions))
            {}
            HWIN_EXPORT FileStream::FileStream(LPCWSTR fileName, FileAccess fileAccess, FileShare fileShare, LPSECURITY_ATTRIBUTES lpSecurityAttributes,FileMode fileMode,FileAttributes attributes,FileOptions fileOptions)
                : Base(Create(fileName, fileAccess, fileShare, lpSecurityAttributes,fileMode,attributes,fileOptions))
            {}

            HWIN_EXPORT FileStream::FileStream(const String& fileName)
                : Base(Create(fileName.c_str(), FileAccess::Default, FileShare::Default, nullptr,FileMode::Default,FileAttributes::Normal,FileOptions::Default))
            {}
            HWIN_EXPORT FileStream::FileStream(LPCWSTR fileName)
                : Base(Create(fileName, FileAccess::Default, FileShare::Default, nullptr,FileMode::Default,FileAttributes::Normal,FileOptions::Default))
            {}

            HWIN_EXPORT FileStream::FileStream(const String& fileName,FileAccess fileAccess)
                : Base(Create(fileName.c_str(), fileAccess, FileShare::Default, nullptr,FileMode::Default,FileAttributes::Normal,FileOptions::Default))
            {}
            HWIN_EXPORT FileStream::FileStream(LPCWSTR fileName,FileAccess fileAccess)
                : Base(Create(fileName, fileAccess, FileShare::Default, nullptr,FileMode::Default,FileAttributes::Normal,FileOptions::Default))
            {}

            HWIN_EXPORT FileStream::FileStream(const String& fileName,FileAccess fileAccess,FileShare fileShare)
                : Base(Create(fileName.c_str(), fileAccess, fileShare, nullptr,FileMode::Default,FileAttributes::Normal,FileOptions::Default))
            {}
            HWIN_EXPORT FileStream::FileStream(LPCWSTR fileName,FileAccess fileAccess,FileShare fileShare)
                : Base(Create(fileName, fileAccess, fileShare, nullptr,FileMode::Default,FileAttributes::Normal,FileOptions::Default))
            {}

            HWIN_EXPORT FileStream::FileStream(const String& fileName,FileAccess fileAccess,FileMode fileMode)
                : Base(Create(fileName.c_str(), fileAccess, FileShare::Default, nullptr,fileMode,FileAttributes::Normal,FileOptions::Default))
            {}
            HWIN_EXPORT FileStream::FileStream(LPCWSTR fileName,FileAccess fileAccess,FileMode fileMode)
                : Base(Create(fileName, fileAccess, FileShare::Default, nullptr,fileMode,FileAttributes::Normal,FileOptions::Default))
            {}

            HWIN_EXPORT FileStream::FileStream(const String& fileName,FileAccess fileAccess,FileShare fileShare,FileMode fileMode)
                : Base(Create(fileName.c_str(), fileAccess, fileShare, nullptr,fileMode,FileAttributes::Normal,FileOptions::Default))
            {}
            HWIN_EXPORT FileStream::FileStream(LPCWSTR fileName,FileAccess fileAccess,FileShare fileShare,FileMode fileMode)
                : Base(Create(fileName, fileAccess, fileShare, nullptr,fileMode,FileAttributes::Normal,FileOptions::Default))
            {}

            HWIN_EXPORT FileStream::FileStream(const String& fileName,FileShare fileShare,FileMode fileMode)
                : Base(Create(fileName.c_str(), FileAccess::Default, fileShare, nullptr,fileMode,FileAttributes::Normal,FileOptions::Default))
            {}
            HWIN_EXPORT FileStream::FileStream(LPCWSTR fileName,FileShare fileShare,FileMode fileMode)
                : Base(Create(fileName, FileAccess::Default, fileShare, nullptr,fileMode,FileAttributes::Normal,FileOptions::Default))
            {}

            HWIN_EXPORT FileStream::FileStream(const String& fileName,FileMode fileMode)
                : Base(Create(fileName.c_str(), FileAccess::Default, FileShare::Default, nullptr,fileMode,FileAttributes::Normal,FileOptions::Default))
            {}
            HWIN_EXPORT FileStream::FileStream(LPCWSTR fileName,FileMode fileMode)
                : Base(Create(fileName, FileAccess::Default, FileShare::Default, nullptr,fileMode,FileAttributes::Normal,FileOptions::Default))
            {}


            HWIN_EXPORT FileStream::FileStream(const String& fileName, FileAccess fileAccess, FileShare fileShare, FileMode fileMode,FileAttributes attributes,FileOptions fileOptions)
                : Base(Create(fileName.c_str(), fileAccess, fileShare, nullptr,fileMode,attributes,fileOptions))
            {}
            HWIN_EXPORT FileStream::FileStream(LPCWSTR fileName, FileAccess fileAccess, FileShare fileShare, FileMode fileMode,FileAttributes attributes,FileOptions fileOptions)
                : Base(Create(fileName, fileAccess, fileShare, nullptr,fileMode,attributes,fileOptions))
            {}



        };
    };
};

